﻿//-----------------------------------------------------------------------
// <copyright file="DoclibTagger.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation.
// All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// This code released under the terms of the 
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
// </copyright>
// <author>Yogesh M Joshi</author>
// <email>ymjoshi@live.com</email>
// <date>16-May-2011</date>
//-----------------------------------------------------------------------

namespace SmartTag.Application.Tagging
{
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Taxonomy;
    using SmartTag.Application.CommandLine;
    using SmartTag.Common.Taxonomy;

    /// <summary>
    /// This class is used to tag items inside given document library
    /// </summary>
    internal class DoclibTagger
    {
        #region "Private Fields"

        /// <summary>
        /// Document library to be tagged
        /// </summary>
        private SPList docLib;

        /// <summary>
        /// Name of the taxonomy columns
        /// </summary>
        private string[] columnNames;

        /// <summary>
        /// Tagger threads
        /// </summary>
        private List<ItemTaggerThread> itemTaggerThreads;

        /// <summary>
        /// Item producer to get items from document library
        /// </summary>
        private Thread itemProducerThread;

        /// <summary>
        /// Queue of items to be tagged
        /// </summary>
        private Queue<SPListItem> itemsToTag = new Queue<SPListItem>();

        /// <summary>
        /// Synchronization object
        /// </summary>
        private object lockObject;

        /// <summary>
        /// Event array to synchronize with worker threads
        /// </summary>
        private ManualResetEvent[] doneEvents;

        /// <summary>
        /// Number of worker threads
        /// </summary>
        private int threadCount;

        /// <summary>
        /// Terms are pre-fetched per column basis in termsRepository
        /// </summary>
        private Dictionary<string, List<Term>> termsRepository = new Dictionary<string, List<Term>>();

        /// <summary>
        /// Fields are pre-identified before we start worker threads
        /// </summary>
        private Dictionary<string, SPField> fieldRepository = new Dictionary<string, SPField>();

        /// <summary>
        /// Number of words to scan from document
        /// </summary>
        private int wordsToScan;

        /// <summary>
        /// Relevance of the term
        /// </summary>
        private int relevance;

        #endregion

        /// <summary>
        /// Initializes a new instance of the DoclibTagger class
        /// </summary>
        /// <param name="docLib">Document library containing items to be tagged</param>
        /// <param name="columnNames">Array of names of taxonomy columns</param>
        /// <param name="wordsToScan">Number of words to scan from document</param>
        /// <param name="relevance">Relevance of the term</param>
        /// <param name="threadCount">Number of worker threads</param>
        public DoclibTagger(
            SPList docLib,
            string[] columnNames,
            int wordsToScan,
            int relevance,
            int threadCount)
        {
            this.docLib = docLib;
            this.columnNames = columnNames;
            this.threadCount = threadCount;
            this.wordsToScan = wordsToScan;
            this.relevance = relevance;
            this.lockObject = new object();
            this.doneEvents = new ManualResetEvent[threadCount];
            this.itemTaggerThreads = new List<ItemTaggerThread>();

            Logger.LogMessage("Initializing tagging operation for document library : " + docLib);
        }

        /// <summary>
        /// Start the tagging operation
        /// </summary>
        public void Start()
        {
            // Initialize
            this.Init(this.docLib, this.columnNames);

            // Check if we have any taxonomy field
            if (this.fieldRepository.Count == 0)
            {
                Logger.LogMessage("No Managed metadata columns found for document library : " + this.docLib.Title);
                return;
            }

            if (this.docLib.ItemCount == 0)
            {
                Logger.LogMessage("No items found for document library " + this.docLib.Title + " skipping...");
                return;
            }

            Logger.LogMessage("Intiailizing threads...");

            // Start Item producer
            this.itemProducerThread = new Thread(new ThreadStart(this.ItemProducerProc));
            this.itemProducerThread.Start();

            // We have to create required no of item tagger threads
            for (int i = 0; i < this.threadCount; i++)
            {
                this.doneEvents[i] = new ManualResetEvent(false);
                ItemTaggerThread thread = new ItemTaggerThread(
                    this.itemsToTag,
                    this.fieldRepository,
                    this.termsRepository,
                    this.wordsToScan,
                    this.relevance,
                    this.lockObject,
                    this.doneEvents[i]);

                ThreadPool.QueueUserWorkItem(thread.TaggerThreadFunc, i);
                this.itemTaggerThreads.Add(thread);
            }

            this.itemProducerThread.Join();
        }

        /// <summary>
        /// Join the item producer thread
        /// </summary>
        public void Join()
        {
            this.itemProducerThread.Join();
        }

        /// <summary>
        /// Initialize this instance
        /// </summary>
        /// <param name="list">Document library</param>
        /// <param name="columnNames">Array of column names</param>
        private void Init(SPList list, string[] columnNames)
        {
            // Clear the old values
            this.termsRepository.Clear();
            this.fieldRepository.Clear();

            SPField taxField = null;
            List<Term> terms = null;

            if (columnNames != null && columnNames.Length > 0)
            {
                foreach (string columnName in columnNames)
                {
                    if (list.Fields.ContainsField(columnName))
                    {
                        taxField = list.Fields[columnName] as TaxonomyField;
                        if (taxField != null
                            && taxField.TypeAsString.Equals("TaxonomyFieldTypeMulti", StringComparison.OrdinalIgnoreCase))
                        {
                            if (this.termsRepository.ContainsKey(taxField.Id.ToString()) == false)
                            {
                                terms = TaxonomyHelper.LoadTerms(taxField);
                                this.termsRepository.Add(taxField.Id.ToString(), terms);
                                this.fieldRepository.Add(taxField.Id.ToString(), taxField);
                            }
                        }
                    }
                }

                return;
            }

            foreach (SPField field in list.Fields)
            {
                taxField = field as TaxonomyField;
                if (taxField != null
                    && taxField.TypeAsString.Equals("TaxonomyFieldTypeMulti", StringComparison.OrdinalIgnoreCase))
                {
                    if (this.termsRepository.ContainsKey(field.Id.ToString()) == false)
                    {
                        terms = TaxonomyHelper.LoadTerms(taxField);
                        this.termsRepository.Add(taxField.Id.ToString(), terms);
                        this.fieldRepository.Add(taxField.Id.ToString(), taxField);
                    }
                }
            }
        }

        /// <summary>
        /// Thread Proc for fetching items one by one from document library
        /// </summary>
        private void ItemProducerProc()
        {
            foreach (SPListItem item in this.docLib.Items)
            {
                // Lock the lock object
                lock (this.lockObject)
                {
                    this.itemsToTag.Enqueue(item);

                    // Wake up the threads waiting
                    Monitor.PulseAll(this.lockObject);
                    Logger.LogMessage("Got item from document library : " + this.docLib.Title);

                    // Wait till some one pulses
                    Monitor.Wait(this.lockObject);
                }
            }

            lock (this.lockObject)
            {
                // Stop all item taggers
                foreach (ItemTaggerThread thread in this.itemTaggerThreads)
                {
                    thread.Stop();
                }

                // Wake up all item taggers
                Monitor.PulseAll(this.lockObject);
            }

            // Wait all threads to complete
            WaitHandle.WaitAll(this.doneEvents);

            Logger.LogMessage("Tagging operation completed for document library : " + this.docLib.Title);
        }
    }
}
